package com.ablanco.zoomy;


import ohos.aafwk.ability.Ability;
import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorProperty;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.components.Image;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.utils.Point;
import ohos.multimodalinput.event.TouchEvent;

import static ohos.agp.components.Component.INVISIBLE;
import static ohos.agp.components.Component.VISIBLE;


class ZoomableTouchListener implements Component.TouchEventListener, ScaleGestureDetector.OnScaleGestureListener {

    private static final int STATE_IDLE = 0;
    private static final int STATE_POINTER_DOWN = 1;
    private static final int STATE_ZOOMING = 2;

    private static final float MIN_SCALE_FACTOR = 1f;
    private static final float MAX_SCALE_FACTOR = 5f;
    private final TapListener mTapListener;
    private final LongPressListener mLongPressListener;
    private final DoubleTapListener mDoubleTapListener;
    private final ScaleGestureDetector mScaleGestureDetector;


    private int mState = STATE_IDLE;
    private ComponentContainer mTargetContainer;
    private Image mTarget;
    private Image mZoomableView;
    private Component mShadow;
    private GestureDetector mGestureDetector;
    private GestureDetector.SimpleOnGestureListener mGestureListener =
        new GestureDetector.SimpleOnGestureListener() {

            @Override
            public boolean onSingleTapConfirmed(TouchEvent ev) {
                if (mTapListener != null) {
                    mTapListener.onTap(mTarget);
                }
                return true;
            }

            @Override
            public void onLongPress(TouchEvent ev) {
                if (mLongPressListener != null) {
                    mLongPressListener.onLongPress(mTarget);
                }
            }

            @Override
            public boolean onDoubleTap(TouchEvent ev) {
                if (mDoubleTapListener != null) {
                    mDoubleTapListener.onDoubleTap(mTarget);
                }
                return true;
            }
        };
    private float mScaleFactor = 1f;
    private Point mCurrentMovementMidPoint = new Point();
    private Point mInitialPinchMidPoint = new Point();
    private Point mTargetViewCords = new Point();


    private boolean mAnimatingZoomEnding = false;

    private int mCurveType;
    private ZoomyConfig mConfig;
    private ZoomListener mZoomListener;

    private Runnable mEndingZoomAction = new Runnable() {
        @Override
        public void run() {

            removeFromDecorView(mShadow);
            removeFromDecorView(mZoomableView);
            mTarget.setVisibility(VISIBLE);
            mZoomableView = null;

            mCurrentMovementMidPoint = new Point();
            mInitialPinchMidPoint = new Point();
            mAnimatingZoomEnding = false;
            mState = STATE_IDLE;

            if (mZoomListener != null) {
                mZoomListener.onViewEndedZooming(mTarget);
            }

            if (mConfig.isImmersiveModeEnabled()) {
                showSystemUI();
            }
        }
    };


    ZoomableTouchListener(ComponentContainer targetContainer,
    Image image,
    ZoomyConfig config,
    ZoomListener zoomListener,
    int curveType,
    TapListener tapListener,
    LongPressListener longPressListener,
    DoubleTapListener doubleTapListener
    ) {

        this.mTargetContainer = targetContainer;
        this.mTarget = image;
        this.mConfig = config;
        this.mCurveType = curveType;
        this.mZoomListener = zoomListener;
        this.mGestureDetector = new GestureDetector(image.getContext(), mGestureListener);
        this.mScaleGestureDetector = new ScaleGestureDetector(image.getContext(), this);
        this.mTapListener = tapListener;
        this.mLongPressListener = longPressListener;
        this.mDoubleTapListener = doubleTapListener;

    }


    private void endZoomingView() {
        if (mConfig.isZoomAnimationEnabled()) {
            mAnimatingZoomEnding = true;
            AnimatorProperty animator = mZoomableView.createAnimatorProperty();
            animator.moveFromX(mCurrentMovementMidPoint.getPointX());
            animator.moveFromY(mCurrentMovementMidPoint.getPointY());
            animator.moveToX(mTargetViewCords.getPointX());
            animator.moveToY(mTargetViewCords.getPointY());
            animator.setCurveType(mCurveType).scaleX(1).scaleY(1);
            animator.setStateChangedListener(new Animator.StateChangedListener() {
                @Override
                public void onStart(Animator animator) {
                }

                @Override
                public void onStop(Animator animator) {
                }

                @Override
                public void onCancel(Animator animator) {
                }

                @Override
                public void onEnd(Animator animator) {
                    mEndingZoomAction.run();
                }

                @Override
                public void onPause(Animator animator) {
                }

                @Override
                public void onResume(Animator animator) {
                }
            });
            animator.start();
        } else {
            mEndingZoomAction.run();
        }
    }


    private void startZoomingView(Image image) {

        if (mConfig.isImmersiveModeEnabled()) {
            hideSystemUI();
        }
        if (mZoomListener != null) {
            mZoomListener.onViewStartedZooming(mTarget);
        }
        mZoomableView = new Image(image.getContext());
        mZoomableView.setLayoutConfig(new ComponentContainer.LayoutConfig(image.getWidth(), image.getHeight()));
        mZoomableView.setPixelMap(image.getPixelMap());

        // 以相同的坐标显示视图
        mTargetViewCords = ViewUtils.getViewAbsoluteCords(image);

        mZoomableView.setContentPosition(mTargetViewCords.getPointX(), mTargetViewCords.getPointY());
        if (mShadow == null) {
            mShadow = new Component(mTarget.getContext());
        }
        if (mShadow.getBackgroundElement() != null) {
            mShadow.getBackgroundElement().setAlpha(0);
        }

        addToDecorView(mShadow);
        addToDecorView(mZoomableView);
        mTarget.setVisibility(INVISIBLE);

        if (mConfig.isImmersiveModeEnabled()) {
            hideSystemUI();
        }
        if (mZoomListener != null) {
            mZoomListener.onViewStartedZooming(mTarget);
        }
    }


    private void addToDecorView(Component component) {
        mTargetContainer.addComponent(component);
    }

    private void removeFromDecorView(Component component) {
        mTargetContainer.removeComponent(component);
    }

    private void obscureDecorView(float factor) {
        // normalize value between 0 and 1
        float normalizedValue = (factor - MIN_SCALE_FACTOR) / (MAX_SCALE_FACTOR - MIN_SCALE_FACTOR);
        normalizedValue = Math.min(0.75f, normalizedValue * 2);
        ShapeElement shapeElement = new ShapeElement();
        shapeElement.setRgbColor(new RgbColor(0, 0, 0, (int) (normalizedValue * 255)));
        mShadow.setBackground(shapeElement);

    }

    private void hideSystemUI() {
        ((Ability) mTarget.getContext()).getWindow().setStatusBarVisibility(INVISIBLE);
    }

    private void showSystemUI() {
        ((Ability) mTarget.getContext()).getWindow().setStatusBarVisibility(VISIBLE);
    }


    /**
     * onTouchEvent
     *
     * @param component component
     * @param touchEvent touchEvent
     * @return boolean
     */
    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        if (mAnimatingZoomEnding || touchEvent.getPointerCount() > 2) {
            return true;
        }
        mScaleGestureDetector.onTouchEvent(touchEvent, component);
        mGestureDetector.onTouchEvent(touchEvent, component);
        int action = touchEvent.getAction();
        switch (action) {
            // 当手指DOWN时统一判断
            case TouchEvent.OTHER_POINT_DOWN:
            case TouchEvent.PRIMARY_POINT_DOWN:
                switch (mState) {
                    case STATE_IDLE:
                        mState = STATE_POINTER_DOWN;
                        break;
                    case STATE_POINTER_DOWN:
                        mState = STATE_ZOOMING;
                        MotionUtils.midPointOfEvent(mInitialPinchMidPoint, touchEvent);
                        startZoomingView(mTarget);
                        break;
                }
            case TouchEvent.POINT_MOVE:
                if (mState == STATE_ZOOMING) {
                    zoomingMove(touchEvent);
                }
                break;
            // 当手指UP时统一判断
            case TouchEvent.OTHER_POINT_UP:
            case TouchEvent.PRIMARY_POINT_UP:
            case TouchEvent.CANCEL:
                switch (mState) {
                    case STATE_ZOOMING:
                        endZoomingView();
                        break;
                    case STATE_POINTER_DOWN:
                        mState = STATE_IDLE;
                        break;
                }
                break;
        }
        return true;
    }

    private void zoomingMove(TouchEvent touchEvent) {
        MotionUtils.midPointOfEvent(mCurrentMovementMidPoint, touchEvent);
        // 因为我们的初始捏合可以在任何视图边缘执行，我们需要减去这个差异并添加系统条高度作为偏移量以避免初始过渡跳跃
        float mCurrentMovementMidPointX = mCurrentMovementMidPoint.getPointX();
        float mCurrentMovementMidPointY = mCurrentMovementMidPoint.getPointY();

        mCurrentMovementMidPointX -= mInitialPinchMidPoint.getPointX();
        mCurrentMovementMidPointY -= mInitialPinchMidPoint.getPointY();
        mCurrentMovementMidPoint.modify(mCurrentMovementMidPointX, mCurrentMovementMidPointY);
        // 因为之前的函数返回的是相对 X,Y 坐标的中点，我们需要添加绝对视图坐标以确保正确的位置
        float mTargetViewCordsX = mTargetViewCords.getPointX();
        float mTargetViewCordsY = mTargetViewCords.getPointY();
        mCurrentMovementMidPointX += mTargetViewCordsX;
        mCurrentMovementMidPointY += mTargetViewCordsY;
        mCurrentMovementMidPoint.modify(mCurrentMovementMidPointX, mCurrentMovementMidPointY);

        mZoomableView.setContentPosition(mCurrentMovementMidPoint.getPointX(), mCurrentMovementMidPoint.getPointY());
    }


    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        if (mZoomableView == null) {
            return false;
        }
        mScaleFactor *= detector.getScaleFactor();
        // Don't let the object get too large.
        mScaleFactor = Math.max(MIN_SCALE_FACTOR, Math.min(mScaleFactor, MAX_SCALE_FACTOR));
        mZoomableView.setScaleX(mScaleFactor);
        mZoomableView.setScaleY(mScaleFactor);
        obscureDecorView(mScaleFactor);
        return true;
    }

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        return mZoomableView != null;
    }

    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {
        mScaleFactor = 1f;
    }
}
